Skip to content
This repository has been archived by the owner on Jul 17, 2022. It is now read-only.

feat: switch over to passport.js #314

Merged
merged 65 commits into from
Sep 15, 2021
Merged

feat: switch over to passport.js #314

merged 65 commits into from
Sep 15, 2021

Conversation

coderbyheart
Copy link
Member

@coderbyheart coderbyheart commented Aug 24, 2021

This adds username/password based authentication,
using a cookie when accessing secure routes

See ./docs/authentication.md for description of the implementation.

  • use email instead of username
  • add email field
  • add password reset
  • confirm email after registration
  • send emails
  • change frontend useAuth hook to use context, so auth state can be access everywhere
  • add ShipmentSendingHub / ShipmentReceivingHub to db schema again
  • figure out what broke sequelize
  • add CNAME to Clever Cloud cellar bucket so app works properly (e.g. index.html is served as DirectoryIndex)
  • send email with password token on password reset
  • new auth forms need some love
  • auto-refresh token on user activity
  • convert clever-cloud infrastructure to code, using the Clever Cloud CLI
  • add CLI for making users admins

CAPTCHA will be handled in a separate PR: #335

Includes changes to host it on CleverCloud, so this PR can be reviewed on a working instance.

@coderbyheart coderbyheart force-pushed the passport.js branch 2 times, most recently from eb7c444 to 06765c0 Compare August 26, 2021 21:20
@ghost
Copy link

ghost commented Aug 26, 2021

Changes to your CodeSee Architecture Map:

View a CodeSee Map of these changes

Legend

CodeSee Map Legend

@coderbyheart coderbyheart force-pushed the passport.js branch 3 times, most recently from 04c6f50 to 2438aaa Compare August 29, 2021 22:23
package.json Outdated
Comment on lines 30 to 55
"@sinclair/typebox": "0.20.5",
"@types/graphql-depth-limit": "1.1.2",
"@types/lodash": "4.14.172",
"@types/supertest": "2.0.11",
"ajv": "8.6.2",
"ajv-formats": "2.1.1",
"apollo-server": "3.3.0",
"apollo-server-express": "3.3.0",
"bcrypt": "5.0.1",
"body-parser": "1.19.0",
"compression": "1.7.4",
"cookie-parser": "1.4.5",
"cors": "2.8.5",
"dotenv": "10.0.0",
"express": "4.17.1",
"graphql": "15.5.1",
"graphql-depth-limit": "1.1.0",
"graphql-import-node": "0.0.4",
"graphql-scalars": "1.10.0",
"lodash": "4.17.21",
"nodemon": "2.0.12",
"passport": "0.4.1",
"passport-cookie": "1.0.9",
"pg": "8.7.1",
"prettier-plugin-organize-imports": "2.3.3",
"reflect-metadata": "0.1.13",
"sequelize": "6.6.2",
"sequelize-typescript": "2.1.0",
"supertest": "6.1.6",
"uuid": "8.3.2"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: thank you for pinning every dependency. I hope it wasn't too much of a pain.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a little, not all of them are on the latest release (e.g. sequelize-typescript)

Comment on lines +8 to +12
i: number
/** user is admin */
a: boolean
/** user hash */
c: string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (non-blocking): are those fields named that way to match the way passport-cookie formats its data?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but we encode this in the cookie sent to the browser, and there is a size limit, so I want to keep it as small as possible (while still using JSON).

@Column
public auth0Id!: string
public username!: string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (non-blocking): why require a username instead of an email?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point. I started this switch without emails, but I realize we need them anyway (for password reset), so I will remove the username field and turn it into an email field.

Comment on lines 37 to 46
if (user === null) {
// Penalize
await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000))
return response.status(401).end()
}
if (!bcrypt.compareSync(valid.value.password, user.passwordHash)) {
// Penalize
await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000))
return response.status(401).end()
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though (non-blocking): this means that if I type the wrong username or password, I'll have to wait 10 seconds until I see an error message, right? I think that could become annoying from a UX perspective. I wonder if we could instead throttle access to that endpoint based on the user's IP (not necessarily in this PR though)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a more advanced feature of this would be remembering the user, however, the point here is to slow down attackers, which most likely will switch IP addresses. I think a better solution will be to implement a CAPTCHA, like https://friendlycaptcha.com/.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the CAPTCHA solution 👍

@coderbyheart
Copy link
Member Author

@deammer I realize that I've build the authentication API outside of GraphQL, as REST. Do you feel that is an issue? They are pretty independent of each other, any way (and were before).

@deammer
Copy link
Collaborator

deammer commented Aug 31, 2021

@deammer I realize that I've build the authentication API outside of GraphQL, as REST. Do you feel that is an issue? They are pretty independent of each other, any way (and were before).

I feel like that's not a problem. The previous /profile endpoint was REST as well and we didn't have any issues.

@coderbyheart coderbyheart force-pushed the passport.js branch 3 times, most recently from 2c39069 to e64eaec Compare September 5, 2021 22:45
@coderbyheart
Copy link
Member Author

I've asked friendlyCaptcha if they would provide us with a free business license, because only that one uses EU-only endpoints.

@coderbyheart
Copy link
Member Author

I have to figure out the problem with sequelize before merging.

This adds username/password based authentication,
using a cookie when accessing secure routes
REACT_APP_VERSION is not used and maintained right now
So Chrome will include it in cross-site requests
Update the DevContainer definition so users are able to run
`npm test`.
@coderbyheart coderbyheart merged commit 550af17 into saga Sep 15, 2021
@coderbyheart coderbyheart deleted the passport.js branch September 15, 2021 19:05
@coderbyheart coderbyheart mentioned this pull request Sep 15, 2021
1 task
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants